feat(obs): content capture for embeddings/rerank/images/audio (LiteLLM parity) - #708
Conversation
…M parity) Completes the AISIX-Cloud#947 family: the four remaining handlers fanned observability events out with content=None, so content_mode=full exporters received metadata only for their traffic. Scope follows LiteLLM's standard logging payload: - embeddings — prompt = post-redaction request JSON; response = the full embeddings JSON, vectors included (LiteLLM logs EmbeddingResponse verbatim), truncated at the capture cap - rerank — prompt = post-redaction request JSON (query + documents); response = the relayed rerank JSON verbatim - images — prompt = post-redaction request JSON; response = the image JSON (url or b64_json; LiteLLM does not strip response b64 either) - audio speech — prompt = post-redaction request JSON (the input text); the binary audio response is not captured (LiteLLM logs no TTS response) - audio transcription/translation — the audio bytes are never captured: the file field is represented by its sha256 (exactly LiteLLM's file_checksum convention) alongside the verbatim text form fields (post-redaction prompt included); response = the post-redaction transcript Blocked (422) responses capture nothing, matching the chat surface. Fixes #700
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR adds optional content capture to audio, embeddings, images, and rerank proxy handlers, and forwards captured request/response content into telemetry fan-out instead of always emitting ChangesContent Capture Implementation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AudioHandler
participant Upstream
participant Telemetry
Client->>AudioHandler: transcription, translation, or speech request
AudioHandler->>AudioHandler: redact input and compute content_capture_cap
AudioHandler->>AudioHandler: build captured_prompt from multipart or JSON body
AudioHandler->>Upstream: forward request
Upstream-->>AudioHandler: transcript, translation, or speech response
AudioHandler->>AudioHandler: build captured_content and attach to success
AudioHandler->>Telemetry: emit_usage_event(captured_content)
Possibly related PRs
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts (1)
266-274: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winResponse-side markers missing from metadata_only negative check.
EMBED_RESPONSE_MARK,RERANK_RESPONSE_MARK, andIMAGES_RESPONSE_URLare asserted present inFULL_LOGSTORE(Lines 206, 220, 232) but are not included in this negative-token list, so the test doesn't verify that response content is excluded from the metadata-only export.🧪 Proposed addition to the negative-token list
for (const token of [ EMBED_PROMPT_TOKEN, + String(EMBED_RESPONSE_MARK), RERANK_PROMPT_TOKEN, RERANK_DOC_TOKEN, + String(RERANK_RESPONSE_MARK), IMAGES_PROMPT_TOKEN, + IMAGES_RESPONSE_URL, SPEECH_PROMPT_TOKEN, TRANSCRIPT_PROMPT_TOKEN, TRANSCRIPT_RESPONSE_TOKEN, ]) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts` around lines 266 - 274, The metadata_only negative-token check in the nontext E2E test is missing response-side markers, so it does not fully verify response content is excluded from the export. Update the token loop in the `sls-content-capture-nontext-e2e` test to include `EMBED_RESPONSE_MARK`, `RERANK_RESPONSE_MARK`, and `IMAGES_RESPONSE_URL` alongside the existing prompt/transcript tokens. Keep the change within the existing negative assertion block so the metadata-only case covers both request and response markers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aisix-proxy/src/audio.rs`:
- Around line 516-518: The full-content export path in audio.rs is leaking
user-controlled upload filenames via the obj.insert call that writes
{name}_filename. Update the logic around the file_name handling so filenames are
either omitted or passed through the same PII redaction flow used elsewhere,
while still keeping the SHA-256-only representation of the file payload. Use the
existing audio export and redaction helpers in this code path to locate the
change.
- Around line 503-521: The captured_prompt builder is losing repeated multipart
fields because serde_json::Map::insert overwrites earlier entries, so preserve
all values instead of collapsing by name. Update the logic inside the
captured_prompt mapping in audio.rs to accumulate repeated fields per key in the
captured content structure (including prompt and other duplicated names), while
keeping the existing sha256/file-name handling for non-text or file fields. Use
the captured_prompt block and the fields iteration as the main place to fix
this.
In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts`:
- Around line 264-276: The negative content checks on the metadata-only logstore
are racing the async export, so the assertion can pass before the event arrives.
In the sls-content-capture-nontext e2e test, update the META_LOGSTORE
verification to wait for a benign always-present marker from the same request,
using the same request flow that already waits on FULL_LOGSTORE, before checking
that EMBED_PROMPT_TOKEN, RERANK_PROMPT_TOKEN, RERANK_DOC_TOKEN,
IMAGES_PROMPT_TOKEN, SPEECH_PROMPT_TOKEN, TRANSCRIPT_PROMPT_TOKEN, and
TRANSCRIPT_RESPONSE_TOKEN are absent. This makes the assertion in the test case
around decodedTextFor and waitForToken order-independent and reliable.
---
Nitpick comments:
In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts`:
- Around line 266-274: The metadata_only negative-token check in the nontext E2E
test is missing response-side markers, so it does not fully verify response
content is excluded from the export. Update the token loop in the
`sls-content-capture-nontext-e2e` test to include `EMBED_RESPONSE_MARK`,
`RERANK_RESPONSE_MARK`, and `IMAGES_RESPONSE_URL` alongside the existing
prompt/transcript tokens. Keep the change within the existing negative assertion
block so the metadata-only case covers both request and response markers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7db54474-bca2-477f-a94c-bbe980f17f21
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
crates/aisix-proxy/Cargo.tomlcrates/aisix-proxy/src/audio.rscrates/aisix-proxy/src/embeddings.rscrates/aisix-proxy/src/images.rscrates/aisix-proxy/src/rerank.rstests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts
…e-race meta assertion CodeRabbit on #708: (1) serde_json::Map::insert overwrote repeated multipart field values in the captured prompt while all repeats are forwarded — append with a newline instead; (2) the upload filename is user-controlled text that skips the redaction path — drop it from the capture, the sha256 alone represents the file (LiteLLM parity anyway); (3) the e2e meta-logstore negative assertions now wait for the last request's metadata to land first, so they can't pass trivially against a lagging store.
Problem
Audit finding #700 (parent: api7/AISIX-Cloud#950), completing the AISIX-Cloud#947 family: embeddings / rerank / images / audio fanned observability events out with
content: None, so acontent_mode = fullexporter received metadata only for their traffic.Scope decision
Per the user's call on #700, the capture scope follows LiteLLM's standard logging payload (verified against LiteLLM HEAD 88e03e54):
EmbeddingResponseverbatim), truncated atcontent_max_bytesurlorb64_json— LiteLLM does not strip response b64 either)inputtext)filefield is represented by its sha256 (LiteLLM'sfile_checksumconvention) alongside the verbatim text form fields (post-redactionpromptincluded)Blocked (422) responses capture nothing, matching the chat surface. All captures are post-#932-masking (builds on #703), so masked PII stays masked in exported content.
Tests
New DP E2E
sls-content-capture-nontext-e2e.test.ts(real binary + etcd + mock SLS): all five scenarios assert thefulllogstore carries the planted request tokens AND the response content (vector value / rerank score / image URL / transcript + file sha256, never the raw audio bytes), whilemetadata_onlyreceives none. Verified fail-before/pass-after (crate changes stashed → test fails).Fixes #700
Summary by CodeRabbit